package com.sheep.loader;

import com.google.common.collect.Lists;
import com.google.common.io.ByteStreams;

import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.zip.ZipEntry;

public final class CustomLoader extends ClassLoader implements Closeable {
    /**
     * 类加载器在类初始化时，通过调用 ClassLoader.registerAsParallelCapable 来标记该加载器支持并行类加载机制。
     * 支持该机制的加载器称之为 可并行 的类加载器。需要注意的是，ClassLoader类是默认可并行加载的，但它的子类仍须通过注册接口调用来支持可并行机制，也就是说，可并行机制不可继承。
     *
     * 在委托结构设计不是很有层次性（如出现闭环委托）的情况下，这些类加载器需要实现并行机制，否则会出现死锁问题。具体可以参考loadClass的函数源码。
     */
    static {
        registerAsParallelCapable();
    }

    /**
     * 自定义加载程序
     */
    private static volatile CustomLoader customLoader;

    /**
     * 对象缓存池
     */
    private final ConcurrentHashMap<String, Object> objectPool = new ConcurrentHashMap<>();

    /**
     * 锁
     */
    private final ReentrantLock lock = new ReentrantLock();

    /**
     * jar包
     */
    private final List<CustomJar> jars = Lists.newArrayList();

    /**
     * 私有化构造方法，避免该类被new出来
     */
    private CustomLoader() {
        super(CustomLoader.class.getClassLoader());
    }

    /**
     * 双重检索，获得实例
     *
     * @return {@link CustomLoader}
     */
    public static CustomLoader getInstance() {
        if (null == customLoader) {
            synchronized (CustomLoader.class) {
                if (null == customLoader) {
                    customLoader = new CustomLoader();
                }
            }
        }
        return customLoader;
    }

    /**
     * 加载扩展jar
     *
     * @param path 路径
     * @return {@link List}<{@link Object}>
     * @throws IOException            io异常
     * @throws ClassNotFoundException 类没有发现异常
     * @throws InstantiationException 实例化异常
     * @throws IllegalAccessException 非法访问异常
     */
    public List<Object> loadExtendJar(final String path) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException {
        File[] jarFiles = JarPathBuilder.getJarPath(path).listFiles(file -> file.getName().endsWith(".jar"));
        if (null == jarFiles) {
            return Collections.emptyList();
        }
        List<Object> results = new ArrayList<>();
        try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
            for (File each : Objects.requireNonNull(jarFiles)) {
                outputStream.reset();
                JarFile jar = new JarFile(each, true);
                jars.add(new CustomJar(jar, each));
                Enumeration<JarEntry> entries = jar.entries();
                while (entries.hasMoreElements()) {
                    JarEntry jarEntry = entries.nextElement();
                    String entryName = jarEntry.getName();
                    if (entryName.endsWith(".class") && !entryName.contains("$")) {
                        String className = entryName.substring(0, entryName.length() - 6).replaceAll("/", ".");
                        Object instance = getOrCreateInstance(className);
                        if (Objects.nonNull(instance)) {
                            results.add(instance);
                        }
                    }
                }
            }
        }
        return results;
    }

    /**
     * 找到类
     *
     * @param name 名字
     * @return {@link Class}<{@link ?}>
     * @throws ClassNotFoundException 类没有发现异常
     */
    @Override
    protected Class<?> findClass(final String name) throws ClassNotFoundException {
        String path = classNameToPath(name);
        for (CustomJar each : jars) {
            ZipEntry entry = each.jarFile.getEntry(path);
            if (Objects.nonNull(entry)) {
                try {
                    int index = name.lastIndexOf('.');
                    if (index != -1) {
                        String packageName = name.substring(0, index);
                        definePackageInternal(packageName, each.jarFile.getManifest());
                    }
                    byte[] data = ByteStreams.toByteArray(each.jarFile.getInputStream(entry));
                    return defineClass(name, data, 0, data.length);
                } catch (final IOException ex) {
                    System.out.println("加载class失败【" + name + "】");
                    ex.printStackTrace();
                }
            }
        }
        throw new ClassNotFoundException(String.format("Class name is %s not found.", name));
    }

    /**
     * 找到资源
     *
     * @param name 名字
     * @return {@link Enumeration}<{@link URL}>
     */
    @Override
    protected Enumeration<URL> findResources(final String name) {
        List<URL> resources = Lists.newArrayList();
        for (CustomJar each : jars) {
            JarEntry entry = each.jarFile.getJarEntry(name);
            if (Objects.nonNull(entry)) {
                try {
                    resources.add(new URL(String.format("jar:file:%s!/%s", each.sourcePath.getAbsolutePath(), name)));
                } catch (final MalformedURLException ignored) {
                }
            }
        }
        return Collections.enumeration(resources);
    }

    /**
     * 找到资源
     *
     * @param name 名字
     * @return {@link URL}
     */
    @Override
    protected URL findResource(final String name) {
        for (CustomJar each : jars) {
            JarEntry entry = each.jarFile.getJarEntry(name);
            if (Objects.nonNull(entry)) {
                try {
                    return new URL(String.format("jar:file:%s!/%s", each.sourcePath.getAbsolutePath(), name));
                } catch (final MalformedURLException ignored) {
                }
            }
        }
        return null;
    }

    /**
     * 关闭
     */
    @Override
    public void close() {
        for (CustomJar each : jars) {
            try {
                each.jarFile.close();
            } catch (final IOException ex) {
                System.out.println("关闭jar");
                ex.printStackTrace();
            }
        }
    }

    /**
     * 获取或创建实例
     *
     * @param className 类名
     * @return {@link T}
     * @throws ClassNotFoundException 类没有发现异常
     * @throws IllegalAccessException 非法访问异常
     * @throws InstantiationException 实例化异常
     */
    @SuppressWarnings("unchecked")
    private <T> T getOrCreateInstance(final String className) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        if (objectPool.containsKey(className)) {
            System.out.println("从缓存中获取的className为【" + className + "】");
            return (T) objectPool.get(className);
        }
        lock.lock();
        try {
            System.out.println("开始创建className为【" + className + "】的实例");
            Object inst = objectPool.get(className);
            if (Objects.isNull(inst)) {
                Class<?> clazz = Class.forName(className, true, this);
                inst = clazz.newInstance();
                objectPool.put(className, inst);
            }
            System.out.println("创建className为【" + className + "】的实例结束");
            return (T) inst;
        } finally {
            lock.unlock();
        }
    }

    /**
     * 类名路径
     *
     * @param className 类名
     * @return {@link String}
     */
    private String classNameToPath(final String className) {
        return String.join("", className.replace(".", "/"), ".class");
    }

    /**
     * 定义包内部
     *
     * @param packageName 包名
     * @param manifest    清单
     */
    private void definePackageInternal(final String packageName, final Manifest manifest) {
        if (null != getPackage(packageName)) {
            return;
        }
        Attributes attributes = manifest.getMainAttributes();
        String specTitle = attributes.getValue(Attributes.Name.SPECIFICATION_TITLE);
        String specVersion = attributes.getValue(Attributes.Name.SPECIFICATION_VERSION);
        String specVendor = attributes.getValue(Attributes.Name.SPECIFICATION_VENDOR);
        String implTitle = attributes.getValue(Attributes.Name.IMPLEMENTATION_TITLE);
        String implVersion = attributes.getValue(Attributes.Name.IMPLEMENTATION_VERSION);
        String implVendor = attributes.getValue(Attributes.Name.IMPLEMENTATION_VENDOR);
        definePackage(packageName, specTitle, specVersion, specVendor, implTitle, implVersion, implVendor, null);
    }

    /**
     * 自定义jar
     *
     * @author 第七人格
     * @date 2023/03/03
     */
    private static class CustomJar {

        /**
         * jar文件
         */
        private final JarFile jarFile;

        /**
         * 源路径
         */
        private final File sourcePath;

        CustomJar(final JarFile jarFile, final File sourcePath) {
            this.jarFile = jarFile;
            this.sourcePath = sourcePath;
        }
    }
}
